package middleware

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gogf/gf/errors/gerror"
	"github.com/msteinert/pam"
	"gitlab.local/DO-module/new-filemanage-module/src/constant"
	"gitlab.local/DO-module/new-filemanage-module/src/controller/errcode"
	"gitlab.local/DO-module/new-filemanage-module/src/core/redisdb"
	utils2 "gitlab.local/DO-module/new-filemanage-module/src/service/access/utils"
	"gitlab.local/DO-module/new-filemanage-module/src/service/data"
	"gitlab.local/TerraMaster/tos-modules/database/sqlitemodel"
	"gitlab.local/golibrary/openssl"
	"gitlab.local/golibrary/resp"
	"gitlab.local/golibrary/utils"
	"gitlab.local/golibrary/wanip"
	"gopkg.in/ini.v1"
	"net/http"
	"strings"
	"time"
)

func (m *MiddleWare) getWanIP() {
	go func() {
		if m.wan.PreTime.IsZero() || time.Now().After(m.wan.PreTime.Add(24*time.Hour)) {
			m.wan.PreTime = time.Now()
			t, err := wanip.New().Get()
			if err == nil {
				m.wan.IP = t
			} else {
				m.wan.PreTime = time.Time{}
			}
		}
	}()
}

func (m *MiddleWare) loadMaxAge() {
	expirationTime := 5 * time.Minute
	filePath := constant.AccountSecret
	if !utils.Exists(filePath) {
		return
	}
	cfg, err := ini.Load(filePath)
	if err != nil {
		return
	}
	section := cfg.Section("")
	logoutTime := section.Key("logout_time").MustInt64(5)
	logoutTimeEnable := section.Key("logout_time_enable").MustBool(false)
	if logoutTimeEnable && logoutTime >= 0 {
		expirationTime = time.Duration(logoutTime) * time.Minute
	} else {
		expirationTime = 0
	}
	m.sessionManage.Handler().SetExpiration(expirationTime)
	m.Save()
}

func (m *MiddleWare) Save() {
	MAKE := "PHPREDIS_SESSION:"
	redisObj, e := redisdb.New()
	if e != nil || redisObj == nil {
		return
	}
	redisDb := redisObj.DbGet()
	if redisDb == nil {
		return
	}
	defer redisDb.Close()
	keys := redisDb.Keys("*").Val()
	for _, key := range keys {
		if ok := strings.Contains(key, MAKE); !ok { // 粗略过滤一下，只有包含 MAKE 的才是 登录状态的session
			continue
		}
		redisStr := redisDb.Get(key).Val()
		_ = m.sessionManage.Handler().Write(key[len(MAKE):], redisStr)
	}
	return
}

// CheckUserPasswd 验证当前用户的密码
func (m *MiddleWare) CheckUserPasswd() gin.HandlerFunc {
	start := time.Now()
	return func(c *gin.Context) {
		u := ParseTosUser(c)
		pass := c.Request.Header.Get("X-CurPass-Token")
		if pass == "" {
			resp.Output(c, nil, gerror.NewCode(errcode.MissRequestHeader, ""), time.Now().Sub(start))
			c.Abort()
			return
		}

		if err := m.verifyPasswd(u.Username, pass); err != nil {
			resp.Output(c, nil, err, time.Now().Sub(start))
			c.Abort()
		}

		c.Next()
	}
}

// UpdateSession 修改Session
func (m *MiddleWare) UpdateSession(c *gin.Context, act string, userInfo sqlitemodel.UserTable) error {
	session, err := m.sessionManage.Start(c.Writer, c.Request)
	if err != nil {
		return errors.New("get session failed")
	}
	expire := m.sessionManage.GetTTL(session.SessionID)
	switch act {
	case "delete", "remove": //清除登录状态
		return m.sessionManage.Remove(session.SessionID)
	case "add", "create": //有时效的登录状态
		session.Value["muser"] = userInfo.Username
		userMap, err := Struct2Map(userInfo)
		if err != nil {
			return err
		}
		session.Value["kod_user"] = userMap
	case "remember": //保持登录状态
		session.Value["muser"] = userInfo.Username
		userMap, err := Struct2Map(userInfo)
		if err != nil {
			return err
		}
		session.Value["kod_user"] = userMap
		expire = -1 * time.Second
	case "share":
		m.sessionManage.ShareID(c.Request, session)
		return nil
	}
	//更新登录状态
	err = m.sessionManage.Save(session)
	if err == nil && expire.Seconds() == -1 {
		return m.sessionManage.Persist(session.SessionID)
	}
	return nil
}

func (m *MiddleWare) SetSession(c *gin.Context, key string, value any) error {
	session, err := m.sessionManage.Start(c.Writer, c.Request)
	if err != nil {
		return errors.New("get session failed")
	}
	session.Value[key] = value
	err = m.sessionManage.Save(session)
	return err
}

func (m *MiddleWare) GetSession(c *gin.Context, key string) string {
	session, err := m.sessionManage.Start(c.Writer, c.Request)
	if err != nil {
		return ""
	}
	if value, ok := session.Value[key]; ok {
		return fmt.Sprintf("%v", value)
	}
	return ""
}

// LoginAuthed 校验是否登录
func (m *MiddleWare) LoginAuthed() gin.HandlerFunc {
	start := time.Now()
	return func(c *gin.Context) {
		if url := c.Request.URL.String(); url == "/login" {
			c.Next()
			return
		}
		if sid := c.Param("sid"); sid != "" {
			c.Request.Header.Set(m.sessionManage.SessionName(), sid)
		}
		sess, err := m.sessionManage.Start(c.Writer, c.Request)
		if err != nil {
			resp.Output(c, nil, gerror.NewCode(errcode.PleaseLogin), time.Now().Sub(start))
			c.Abort()
			return
		}
		_, ok := sess.Value["muser"]
		if !ok {
			resp.Output(c, nil, gerror.NewCode(errcode.PleaseLogin), time.Now().Sub(start))
			c.Abort()
			return
		}
		kodUser, _ := sess.Value["kod_user"]
		jsonStr, err := json.Marshal(PhpValue2Map(kodUser))
		if err != nil {
			resp.Output(c, nil, gerror.Wrap(err, "get login info error"), time.Now().Sub(start))
			c.Abort()
			return
		}
		temp := sqlitemodel.UserTable{}
		_ = json.Unmarshal(jsonStr, &temp)

		// Check if user is disabled or expired
		expire := data.ExpireDays(temp.Username, m.dbGetter.DbGet())
		if (expire != 0 && expire != 1 && time.Now().Unix()-expire >= 0) || expire == 1 {
			resp.Output(c, nil, gerror.NewCode(errcode.AccountDisabled, ""), time.Now().Sub(start))
			c.Abort()
			return
		}

		if !utils2.GetOneAccessInfo(m.dbGetter.DbGet(), temp.Username, "TOS") {
			resp.Output(c, false, gerror.NewCode(errcode.ForbidLogin, ""), 0)
			c.Abort()
			return
		}
		//判断已登录，保存session中的信息到上下文变量中
		c.Set(constant.ContextUserInfo, jsonStr)
		//更新session|cookie

		_ = m.sessionManage.Save(sess)
		SetCookie(c, constant.CookieCurrentUserName, temp.Username, time.Now().Add(time.Hour*24).Second())

		//只要有请求，就异步操作持久化
		go m.hookApi.Persistence()
		if temp.Role != "root" {
			_ = utils.SwitchUserRight(temp.Username)
		}
		c.Next()
		return
	}
}

// AuthService 服务验证用户密码
func (m *MiddleWare) AuthService(username string, password string) error {
	days := data.ExpireDays(username, m.dbGetter.DbGet())
	if days == 1 {
		return gerror.NewCode(errcode.AccountDisabled)
	}
	if days > 1 {
		if time.Unix(days, 0).Sub(time.Now()).Seconds() <= 0 {
			return gerror.NewCode(errcode.AccountExpired)
		}
	}
	t, err := pam.StartFunc("tos", username, func(s pam.Style, msg string) (string, error) {
		switch s {
		case pam.PromptEchoOff:
			return password, nil
		case pam.PromptEchoOn, pam.ErrorMsg, pam.TextInfo:
			return "", errors.New(msg)
		}
		return "", errors.New("unknown PAM message style")
	})
	if err != nil {
		return gerror.WrapCode(errcode.AuthenticationFailed, err)
	}
	defer t.CloseSession(pam.Silent)
	if err = t.Authenticate(pam.Silent); err != nil {
		return gerror.WrapCode(errcode.AuthenticationFailed, err)
	}
	return nil
}

// Cors 跨域中间件
func (m *MiddleWare) Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		if c.GetHeader("X-Requested-With") == "io.dcloud.HBuilder" {
			c.Header("Access-Control-Allow-Origin", "*")
		} else {
			c.Header("Access-Control-Allow-Origin", "same-origin")
		}
		c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
		c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, x-csrf-token")
		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
		c.Header("Access-Control-Allow-Credentials", "true")
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
		c.Next()
	}
}

func (m *MiddleWare) verifyPasswd(username, passwd string) error {
	orgPass, err := openssl.JsEcryptDecode(passwd)
	if err != nil {
		return gerror.WrapCode(errcode.InvalidRsaCode, err, "Decryption failed")
	}

	return m.AuthService(username, orgPass)
}
